import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;

public class fuzzer_mutate {
	private static boolean replay = false;
	private static int gseed = 0;
	private static boolean fuzz=false;
	private static String baseFile = "sample.jpg";
	private static boolean timeoutFail = false;
	private static boolean timeoutOk = false;
	private static long time = 3600*5;//default timeout is 5 hours
	private static String cmd ="";
	private static String cmdFile = "sample.jpg";
	private static int count = 1;
	
    //parse the command line
	static void parseCmd(String cmdLine[], int len){
		if (len == 0 || len == 1) {
			System.out.println("Invalid arguments");
			System.out.println("fuzzer [OPTIONS] CMD [CMDARG]");
		} else {
			//parsing the commandline argument
			for(int i =0; i<len; i++){
				if(cmdLine[i].equalsIgnoreCase("--replay")){
					try {
						
						gseed = Integer.parseInt(cmdLine[i+1]);
		            } catch (NumberFormatException e) {
		                System.err.println("Seed for --replay must be an integer.");
		                System.exit(1);
		            }
		            replay = true;
					//System.out.println("given replay seed is:"+gseed);
				}else if(cmdLine[i].equalsIgnoreCase("--fuzz-file")){
					fuzz = true;
					baseFile = cmdLine[i+1];
				}else if(cmdLine[i].equalsIgnoreCase("--timeout-fail")){
					if (timeoutOk==true){
						System.out.println("--timeout-ok mode is on");
						System.exit(1);
					}
					try {
						time = Long.parseLong(cmdLine[i+1]);
		            } catch (NumberFormatException e) {
		                System.err.println("Argument must be an integer.");
		                System.exit(1);
		            }
					timeoutFail = true;
				}else if(cmdLine[i].equalsIgnoreCase("--timeout-ok")){
					if(timeoutFail == true){
						System.out.println("--timeout-fail mode is on");
						System.exit(-1);
					}
					try {
						time = Long.parseLong(cmdLine[i+1]);
		            } catch (NumberFormatException e) {
		                System.err.println("Argument must be an integer.");
		                System.exit(1);
		            }
					timeoutOk = true;
				}else if(i==len-1){		
					cmdFile = cmdLine[i];
					if(fuzz==false)
						baseFile = cmdFile;
				}else{
					cmd = cmd+cmdLine[i]+" ";
					i--;
				}
				i++;
			}//end of for loop for parsing command line
		}//end of if-else
	}//end of parseCmd
	//print the command line input for replay mode
	static void printCmdLine(String cmdLine[], int len){
		String input = "fuzzer";
		for(int i =0; i<len; i++){
			input = input + " "+ cmdLine[i];
		}
		System.out.println(input);
	}
    //find the length of the file

	//find the length of the baseFile
	static int findLen(){
		FileInputStream fin;
		int i = 0;
		try {
			fin = new FileInputStream (baseFile);
		    // Read a byte of file
		    try {
				while(fin.available()!=0){
					fin.read();
					i++;
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	    return i;
	}
	//append data to the end of a file with given name
	static void writeToFile(String name, String data){
		try{
			FileWriter writer = new FileWriter(name, true);
			writer.write(data);
			writer.write('\n');
			writer.close();
		}catch(IOException ex){
			ex.printStackTrace();
		}
	}
	
	//create a test file
	static void createTest(int pos, int mode, int b){
		//create a testfile by:
		//mutate baseFile at location pos
		//using given mode and muate it b times
		FileInputStream fin;		
		FileOutputStream fout;
		
		int choice = gseed%7;
		if(choice<0)
			choice *= -1;
		
		try{
		    // Open the input stream, and create an output stream
		    fin = new FileInputStream (baseFile);
		    fout = new FileOutputStream ("test.jpg");
		    
		    //System.out.println(pos +", "+choice+", "+mode);
		    int i = 0;
		    while(fin.available()!=0){
		    	//Read a byte of file
		    	int readByte = fin.read();
		    	i++;
		    	if(i==pos){
		    	  while(b > 0){
		    		switch(choice){
		    			case 0://no change
		    				break;
		    			case 1://mutate
		    				readByte = mutate(readByte, mode);
		    				break;
		    			case 2://delete
		    				readByte = fin.read();
		    				break;
		    			case 3://random insert
		    				fout.write(gseed);
		    				break;
		    			case 4://duplicate insert
		    				fout.write(readByte);
		    				break;
		    			default://mutate
		    				readByte = mutate(readByte, mode);
		    		}//end of switch based on choice
		    		b--;
		    	  }//end of while(b>0)
		    	}//end of if(pos) is the given position
		    	fout.write(readByte);
		    }//end of while still things to read
		    // Close our input and output stream
		    fin.close();	
		    fout.close();
		}catch (IOException e){
			System.err.println ("Unable to read from file");
			System.exit(1);
		}//end of try-catch block
	}
	//mutate a read byte
	static int mutate(int readByte, int mode){
		switch(mode){
			case 0:	//no fuzzing
				break;
			case 1:
				readByte = readByte + gseed;
				break;
			case 2:
				readByte ^= readByte*gseed+gseed;
				break;
			case 3:
				readByte = readByte & 1;
				break;
			case 4:
				readByte = gseed;
				break;
			case 5:
				readByte = readByte >> 4;
				break;
			case 6:
				readByte = readByte & gseed;
				break;
			case 7:
				readByte = readByte*-1;
				break;
			case 8:
				readByte = readByte|gseed;
				break;
			case 9:
				readByte = readByte^gseed;
				break;
			default:
				readByte ^= readByte;
		}///end of switch	  
		return readByte;
	}
    //fuzzing the file: create test file, run cmd, record
    static String fuzzingFile(int pos, int mode, int b) {     
        //if --fuzz-file is NOT on, just run cmd with original file
        if(!fuzz)
        	return runCMD(cmdFile);
        createTest(pos, mode, b);
        return runCMD("test.jpg");
    }
    //run the command
    static String runCMD(String testFile){
    	//running a command in java
		String command="./"+cmd+testFile;
		String result="";
		Process child;
		try {
			child = Runtime.getRuntime().exec(command);
			InputStream inputstream =
	            child.getErrorStream();
			//child.getOutputStream();
	        InputStreamReader inputstreamreader =
	            new InputStreamReader(inputstream);
	        BufferedReader bufferedreader =
	            new BufferedReader(inputstreamreader);
	        
	        result = bufferedreader.readLine();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			result = e.getMessage();
			e.printStackTrace();
		}
		if(result==null)
			return "";
		return result;
    }
  	
	//timer
    static class MyTimer implements Runnable {
        public void run() { 
            try{
            	while(true)
            		Thread.sleep(100);	
            }catch (InterruptedException e) {
                System.out.println("fuzzer: but I wasn't done!");
            }
        }
    }
	//things to do if timeout
    static void doTimeout(){
    	//things to do when time is up
        if(timeoutFail){
        	//record and print seed to screen
        	writeToFile("log.txt", gseed+": timeout-failed");
        	System.out.println(gseed);
        }
        if(timeoutOk){
        	//record but not print the seed to screen
        	writeToFile("log.txt", gseed+": timeout-oked");
        }
        System.out.println("Too bad, it's over!");
    }
    //main function
	public static void main(String args[]) throws InterruptedException {
		//parse the commandline
		parseCmd(args,args.length);
		
		//create our random generator
		Random rand = new Random();
		if(replay){
			rand = new Random(gseed);
		}else{
			//generate a random integer seed
			gseed = rand.nextInt();
			//create a random geneartor using the seed
			rand = new Random(gseed);
		}

        //make sure we clear the log file
        try {
			Runtime.getRuntime().exec("rm log.txt");
		} catch (IOException e) {
			e.printStackTrace();
		}
		
        //change given time argument from seconds to miliseconds
        long timeOut = 1000 * time;

        //System.out.println("Preparing the timer to start...");
        long startTime = System.currentTimeMillis();
        Thread timer = new Thread(new MyTimer());
        timer.start();

        //loop until fuzzingFile thread exits
        while (timer.isAlive()) {
        	//Wait maximum of 10 miliseconds for timer finish.
        	timer.join(10);

    		int mode = 0;
    		int pos = 0;
    		int b = 1;
    		String result = "";
    		if(fuzz){
    			mode = rand.nextInt(10);
    			pos = rand.nextInt(25);
    		}

            //run the method to fuzz the file and get output result
    		result = fuzzingFile(pos, mode, b);
            if(result.matches("BUG \\d TRIGGERED")||result.matches("BUG \\d\\d TRIGGERED"))
            	System.out.println(gseed);
            
            result = String.valueOf(count)+": "+String.valueOf(gseed)+": "+result;
            writeToFile("log.txt", result);            
           
            count++;
            
            if(replay){
            	printCmdLine(args, args.length);
            	System.exit(1);
            }
            
            try {
				Runtime.getRuntime().exec("rm foo.ppm");
				Runtime.getRuntime().exec("rm test.jpg");
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} //remove the file
            
            if (((System.currentTimeMillis() - startTime) > timeOut) &&
                    timer.isAlive()) {
            	//System.out.println(startTime +", "+System.currentTimeMillis());
                System.out.println("Time is up!");
                
                //things to do when time is up
                doTimeout();
                //System.exit(1);
                //interrupt the thread
                timer.interrupt();
                //kill the thread, wait at most 1 millisecond
                timer.join(1);
            }
            
    		//create new random number generator with a new seed
            gseed = rand.nextInt();
            rand = new Random(gseed);
        }//end of while loop for timer.isAlive
    }//end of main

}